package dmwr.repo.gae;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilter;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;

import dmwr.Dmwr;
import dmwr.bean.Application;
import dmwr.bean.Entry;
import dmwr.repo.EntryRepo;

public class EntryRepoGae implements EntryRepo {
	private static final Logger log = Logger.getLogger(EntryRepoGae.class
			.getName());
	private static final String KIND = "Entry";
	private static final String PROPERTY_POOL_ID = "poolId";
	private static final String PROPERTY_TEXT1 = "text1";
	private static final String PROPERTY_TEXT2 = "text2";
	private static final String PROPERTY_CREATED = "created";

	@Override
	public List<Entry> list(int offset, int limit) {
		return entries(new Query(KIND).addSort(PROPERTY_CREATED,
				Query.SortDirection.DESCENDING), offset, limit);
	}

	@Override
	public Long count() {
		return Dmwr.applicationRepo().get().getEntryCount();
	}

	@Override
	public Entry byId(Long id) {
		Key key = KeyFactory.createKey(KIND, id);
		Entry item;
		try {
			item = entry(Gae.datastoreService().get(key));
		} catch (EntityNotFoundException e) {
			item = null;
		}
		log.info("entry byId " + id + " " + item);
		return item;
	}

	@Override
	public void save(Entry entry) {
		Entity entity;
		if (entry.getId() == null) {
			entity = new Entity(KIND);
			entity.setProperty(PROPERTY_CREATED, System.currentTimeMillis());
			Application application = Dmwr.applicationRepo().get();
			application.setEntryCount(application.getEntryCount() + 1);
			Dmwr.applicationRepo().save(application);
		} else {
			// entity = new Entity(KIND, entry.getId());
			Key key = KeyFactory.createKey(KIND, entry.getId());
			try {
				entity = Gae.datastoreService().get(key);
			} catch (EntityNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
		entity.setProperty(PROPERTY_POOL_ID, entry.getPoolId());
		entity.setProperty(PROPERTY_TEXT1, entry.getText1());
		entity.setProperty(PROPERTY_TEXT2, entry.getText2());
		Gae.datastoreService().put(entity);
		if (entry.getId() == null) {
			entry.setId(entity.getKey().getId());
		}
	}

	@Override
	public void delete(Long id) {
		Key key = KeyFactory.createKey(KIND, id);
		Gae.datastoreService().delete(key);
		Application application = Dmwr.applicationRepo().get();
		application.setEntryCount(application.getEntryCount() - 1);
		Dmwr.applicationRepo().save(application);
	}

	@Override
	public void deleteAll() {
		Query query = new Query(KIND).addSort(PROPERTY_CREATED,
				Query.SortDirection.ASCENDING);
		List<Entity> results;
		while (!(results = Gae.datastoreService().prepare(query)
				.asList(FetchOptions.Builder.withLimit(100))).isEmpty()) {
			for (Entity result : results) {
				Gae.datastoreService().delete(result.getKey());
			}
		}
		Application application = Dmwr.applicationRepo().get();
		application.setEntryCount(0L);
		Dmwr.applicationRepo().save(application);
	}

	@Override
	public Entry firstCreatedAfter(Long poolId, Long created) {
		Collection<Filter> subFilters = new ArrayList<Query.Filter>();
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_POOL_ID, poolId));
		subFilters.add(FilterOperator.GREATER_THAN
				.of(PROPERTY_CREATED, created));
		Entry item = entry(new Query(KIND).setFilter(
				new CompositeFilter(CompositeFilterOperator.AND, subFilters))
				.addSort(PROPERTY_CREATED, Query.SortDirection.ASCENDING));
		log.info("entry firstCreatedAfter " + created + " " + item);
		return item;
	}

	@Override
	public Entry firstByText(Long poolId, String text1, String text2) {
		Collection<Filter> subFilters1 = new ArrayList<Query.Filter>();
		subFilters1.add(FilterOperator.EQUAL.of(PROPERTY_POOL_ID, poolId));
		Collection<Filter> subFilters2 = new ArrayList<Query.Filter>();
		subFilters2.add(FilterOperator.EQUAL.of(PROPERTY_TEXT1, text1));
		subFilters2.add(FilterOperator.EQUAL.of(PROPERTY_TEXT2, text2));
		subFilters1.add(new CompositeFilter(CompositeFilterOperator.OR,
				subFilters2));
		return entry(new Query(KIND).setFilter(
				new CompositeFilter(CompositeFilterOperator.AND, subFilters1))
				.addSort(PROPERTY_CREATED, Query.SortDirection.ASCENDING));
	}

	private List<Entry> entries(Query query, int offset, int limit) {
		List<Entity> results = Gae.datastoreService().prepare(query)
				.asList(FetchOptions.Builder.withOffset(offset).limit(limit));
		List<Entry> list = new ArrayList<Entry>(results.size());
		for (Entity result : results) {
			list.add(entry(result));
		}
		return list;
	}

	private Entry entry(Query query) {
		List<Entity> results = Gae.datastoreService().prepare(query)
				.asList(FetchOptions.Builder.withLimit(1));
		Entity result = !results.isEmpty() ? results.get(0) : null;
		return entry(result);
	}

	private Entry entry(Entity result) {
		return result != null ? new Entry(result.getKey().getId(),
				(Long) result.getProperty(PROPERTY_POOL_ID),
				(String) result.getProperty(PROPERTY_TEXT1),
				(String) result.getProperty(PROPERTY_TEXT2),
				(Long) result.getProperty(PROPERTY_CREATED)) : null;
	}
}
